package cn.yuanqiao.es.service.impl;


import cn.yuanqiao.common.core.domain.AjaxResult;
import cn.yuanqiao.common.utils.SecurityUtils;
import cn.yuanqiao.es.config.ESClientPool;

import cn.yuanqiao.es.domain.ElasticsearchQuery;
import cn.yuanqiao.es.domain.QueryFile;
import cn.yuanqiao.es.domain.QueryParameter;
import cn.yuanqiao.es.mapper.ElasticsearchApiMapper;
import cn.yuanqiao.es.service.ElasticsearchTemplate;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.mapping.FieldType;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggester;
import co.elastic.clients.elasticsearch.core.search.FieldSuggester;
import co.elastic.clients.elasticsearch.core.search.Suggester;
import co.elastic.clients.elasticsearch.core.search.TrackHits;
import co.elastic.clients.elasticsearch.indices.GetMappingRequest;
import co.elastic.clients.elasticsearch.indices.GetMappingResponse;
import co.elastic.clients.json.JsonData;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static org.elasticsearch.search.internal.SearchContext.DEFAULT_TRACK_TOTAL_HITS_UP_TO;

/**
 * @ProjectName: ruoyi-modules-system
 * @Package: cn.yuanqiao.es.service.impl
 * @ClassName: ElasticsearchTemplateImpl
 * @Author: lenovo
 * @Description: hbWang
 * @Date: 2023/6/13 16:14
 * @Version: 1.0
 */

@Slf4j
@Component
public class ElasticsearchTemplateImpl implements ElasticsearchTemplate {

    @Autowired
    ElasticsearchApiMapper elasticsearchApiMapper;

    /**
     * web端全文检索实现
     *
     * @param query
     * @param indexName
     * @return
     */
    @Override
    public SearchResponse<Object> search(QueryFile query, String indexName) {
        ElasticsearchClient client = null;
        SearchResponse<Object> searchResponse = null;
        try {
            client = ESClientPool.getClient();
            List<Query> queries = queryList(query, indexName);
            //排序
            List<SortOptions> sorts = new ArrayList<>();
            if (!"".equals(query.getOrder()) && null != query.getOrder()) {
                String[] strArray = query.getOrder().split(" ");
                String result1 = strArray[1];
                String filename = result1.toLowerCase();
                String field = strArray[0];
                GetMappingRequest request = new GetMappingRequest.Builder().index(indexName.toLowerCase()).build();
                GetMappingResponse mapping = client.indices().getMapping(request);
                String jsonValue = mapping.result().get(indexName.toLowerCase()).mappings().properties().get(field)._kind().jsonValue();
                if ("text".equals(jsonValue)) {
                    field = field + ".keyword";
                }
                String finalField = field;
                if ("asc".equals(filename)) {
                    sorts.add(SortOptions.of(s -> s.field(f -> f.field(finalField).unmappedType(FieldType.Long).order(SortOrder.Asc))));
                } else if ("desc".equals(filename)) {
                    sorts.add(SortOptions.of(s -> s.field(f -> f.field(finalField).unmappedType(FieldType.Long).order(SortOrder.Desc))));
                }
            }
            SearchRequest searchQuery = new SearchRequest.Builder().index(indexName.toLowerCase())
                    .trackTotalHits(TrackHits.of(t -> t.enabled(true)))
                    .query(q -> q.bool(b -> b.must(queries)))
                    //过滤字段
                    .source(s -> s.filter(f -> f.excludes("pathDocText", "pinyin_basis_data")))
                    .from((query.getPageNum() - 1) * query.getPageSize())
                    .size(query.getPageSize())
                    .sort(sorts)
//                    .highlight(h->h.fields("*",q->q.preTags("<span style='color: red;'>").postTags("</span>"))
//                    )
                    .build();
            log.info("查询SQL{}", searchQuery);
            searchResponse = client.search(searchQuery, Object.class);
        } catch (Exception e) {
            e.printStackTrace();
            AjaxResult.error("失败");
        } finally {
            ESClientPool.returnClient(client);
        }
        return searchResponse;
    }


    /**
     * 联想词查询
     *
     * @return
     */
    @Override
    public SearchResponse<Object> searchAssociate(String key, String indexName) {
        ElasticsearchClient client = null;
        SearchResponse searchResponse = null;
        try {
            client = ESClientPool.getClient();
            SearchRequest searchQuery = new SearchRequest.Builder().index(indexName.toLowerCase())
                    .suggest(Suggester.of(s -> s
                            .suggesters("my-suggest", FieldSuggester.of(f -> f
                                    .completion(CompletionSuggester.of(
                                            c -> c.size(10)
                                                    .field("TITLE")
                                                    .skipDuplicates(true)
                                    ))))
                            .text(key)
                    ))
                    .build();
            log.info("查询SQL{}", searchQuery);
            searchResponse = client.search(searchQuery, Object.class);
        } catch (Exception e) {
            e.printStackTrace();
            AjaxResult.error("失败");
        } finally {
            ESClientPool.returnClient(client);
        }
        return searchResponse;
    }

    /**
     * 判断电子文件里有哪些字段匹配
     *
     * @param query
     * @return
     */
    @Override
    public String queryMultiMatch(ElasticsearchQuery query) {
        ElasticsearchClient client = null;
        SearchResponse<Object> searchResponse = null;
        List<String> list = new ArrayList<>();
        try {
            client = ESClientPool.getClient();
            for (String value : query.getQueryList()) {
                SearchRequest searchQuery = new SearchRequest.Builder().index(query.getIdexName().toLowerCase())
                        .query(q -> q.bool(b -> b.must(m -> m.multiMatch(a -> a.query(value).fields("pathDocText")))
                                .must(f -> f.term(t -> t.field("ID.keyword").value(query.getDaId())))))
                        .build();
                searchResponse = client.search(searchQuery, Object.class);
                long total = searchResponse.hits().total().value();
                if (total != 0) {
                    list.add(value);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            AjaxResult.error("失败");
        } finally {
            ESClientPool.returnClient(client);
        }
        return String.join(",", list);
    }

    /**
     * 小程序查询
     *
     * @param query
     * @param indexName
     * @return
     */
    @Override
    public SearchResponse<Object> searchApp(QueryFile query, String indexName) {
        ElasticsearchClient client = null;
        SearchResponse<Object> searchResponse = null;
        try {
            client = ESClientPool.getClient();
            // 将多个查询条件放入list中.
            List<Query> queries = new ArrayList<>();
            if (org.apache.commons.lang3.StringUtils.isNotBlank(query.getFileField())) {
                queries.add(initMatchQuery(query.getFileField()));
            }
            if (!"".equals(query.getCategoryId()) && null != query.getCategoryId()) {
                queries.add(TermQuery.of(m -> m.field("categoryId.keyword").value(query.getCategoryId())
                )._toQuery());
            }
            int from = 0;
            if (query.getPageNum() != 0) {
                String sss = String.valueOf(query.getPageNum());
                int num = Integer.parseInt(sss.substring(0, sss.length() - 1));
                from = (num - 1) * query.getPageSize();
            }
            SearchRequest searchQuery = new SearchRequest.Builder().index(indexName.toLowerCase())
                    .trackTotalHits(TrackHits.of(t -> t.enabled(true)))
                    .query(q -> q
                            .bool(b -> b.must(queries)
                            ))
                    //过滤字段
                    .source(s -> s.filter(f -> f.excludes("pathDocText", "pinyin_basis_data")))
                    .from(from)
                    .size(query.getPageSize())
                    .build();

            searchResponse = client.search(searchQuery, Object.class);
        } catch (Exception e) {
            e.printStackTrace();
            AjaxResult.error("失败");
        } finally {
            ESClientPool.returnClient(client);
        }
        return searchResponse;

    }

    /**
     * 处理查询条件
     *
     * @param query
     * @return
     */
    public List<Query> queryList(QueryFile query, String indexName) {

        // 将多个查询条件放入list中.
        List<Query> queries = concatQueryCondition(query, indexName);
        if (!"".equals(query.getCategoryId()) && null != query.getCategoryId()) {
            String[] ids = query.getCategoryId().split(",");
            if (ids.length > 1) {
                List<FieldValue> valueList = new ArrayList<>();
                for (String id : ids) {
                    valueList.add(FieldValue.of(id));
                }
                queries.add(TermsQuery.of(m -> m.field("categoryId.keyword").terms(t -> t.value(valueList))
                )._toQuery());
            } else {
                queries.add(TermQuery.of(m -> m.field("categoryId.keyword").value(query.getCategoryId())
                )._toQuery());
            }
        } else {
            return null;
        }
        return queries;
    }


    private Query initQueryStringFiled(String fieldValue) {
        return StringUtils.isNotBlank(fieldValue) ? QueryStringQuery.of(q ->
                q.query(fieldValue)
        )._toQuery() : null;
    }


    /**
     * match查询语句.
     *
     * @param fieldValue 查询字段值
     * @return query语句
     */
    private Query initMatchQueryFiled(String fieldValue, List<String> fileds) {
        return StringUtils.isNotBlank(fieldValue) ? MultiMatchQuery.of(m -> m
                .query(fieldValue).fields(fileds)
        )._toQuery() : null;
    }


    /**
     * match查询语句.
     *
     * @param fieldValue 查询字段值
     * @return query语句
     */
    private Query initMatchQuery(String fieldValue) {
        return StringUtils.isNotBlank(fieldValue) ? MultiMatchQuery.of(m -> m
                .query(fieldValue)
        )._toQuery() : null;
    }

    /**
     * match查询语句.同义词
     *
     * @param query 查询字段值
     * @return query语句
     */
    private Query initMatchQueryTYC(List<Query> query) {
        return Query.of(q -> q.bool(b -> b.should(query)));
    }


    /**
     * matchPhrase
     *
     * @param fieldName  查询字段名称
     * @param fieldValue 查询字段集合
     * @return query语句
     */
    private Query initMatchPhraseQuery(String fieldName, String fieldValue) {
        return MatchPhraseQuery.of(m -> m.field(fieldName).query(fieldValue).slop(0)
        )._toQuery();
    }


    /**
     * Wildcard    字段值前后要加通配符 *可以代表 0 至多个字符   ? 只能代表一位
     * 查询效率低
     *
     * @param fieldName  查询字段名称
     * @param fieldValue 查询字段值
     * @return query语句
     */
    private Query initWildcardQuery(String fieldName, String fieldValue) {
        return WildcardQuery.of(m -> m.field(fieldName).value(fieldValue)
        )._toQuery();
    }


    /**
     * range查询语句. 区间
     *
     * @param fieldName 查询字段名称
     * @param begin     开始时间
     * @param end       结束时间
     * @return query语句
     */

    private Query initRangeQuery(String fieldName, String begin, String end) {
        return RangeQuery.of(m -> m
                .field(fieldName)
                .gte((JsonData.of(begin)))
                .lte((JsonData.of(end)))
        )._toQuery();
    }


    /**
     * terms查询语句
     *
     * @param fieldName  查询字段名称
     * @param fieldValue 查询字段集合
     * @return query语句
     */
    private Query initTermsQuery(String fieldName, List<String> fieldValue) {
        List<FieldValue> valueList = new ArrayList<>();
        //转换成FieldValue
        fieldValue.forEach(f ->
                valueList.add(FieldValue.of(f)));
        return TermsQuery.of(m -> m.field(fieldName).terms(t -> t.value(valueList))
        )._toQuery();
    }

    //拼接前端查询条件
    public List<Query> concatQueryCondition(QueryFile queryFile, String indexName) {
        List<Query> queries = new ArrayList<>();
        //拼接前端查询条件
        if (queryFile.getQueryParameters() != null && queryFile.getQueryParameters().size() > 0) {
            for (QueryParameter q : queryFile.getQueryParameters()) {
                String[] queryValue = new String[2];    //查询值
                String queryType = q.getQueryType();      //查询类型
                String showType = q.getShowType();        //展示类型
                String fieldName = q.getColumn();
                if (org.apache.commons.lang3.StringUtils.isBlank(q.getValue())) {
                    continue;
                }
                if ("4".equals(queryType)) {    //区间查询会有2个值
                    queryValue[0] = q.getValue();        //根据查询项获取对应的查询值
                    queryValue[1] = q.getValueTwo();
                } else {
                    queryValue[0] = q.getValue();        //根据查询项获取对应的查询值
                }
                queries.add(getQueryCriteriaByType(fieldName, showType, queryType, queryValue, indexName));
                //logger.debug("queryColumns["+j+"] = "+queryColumns[j]+"--"+queryValue[0]);
                //  queries.addAll(getQueryCriteriaByType(fieldName, showType, queryType, queryValue,queryFile.getMenuId()));
            }
            ;
        }
        return queries;
    }

    /**
     * <p>Title: getQueryCriteriaByType</p>
     * <p>Description: 根据传入的类型type 组合查询条件</p>
     *
     * @param column   列名
     * @param showType 查询类型
     *                 0	不参加查询	暂不考虑;
     *                 1	精确查询		= '匹配值';
     *                 2	模糊查询		like '%匹配值%';
     *                 3	左匹配查询	like '匹配值%';
     *                 4	区间查询		'匹配值1'<列<'匹配值2'	?是否包含=;
     *                 5	列表查询		不做处理;
     *                 6	单选对话框	不做处理;
     *                 7	多选对话框	不做处理;
     *                 8	多词检索		分词 以空格为标志;
     *                 9	全文精确查询;
     * @param values   查询值
     * @return
     */
    public Query getQueryCriteriaByType(String column, String showType, String queryType, String[] values, String indexName) {
        Integer type = Integer.parseInt(queryType);
        if (org.apache.commons.lang3.StringUtils.isNotBlank(column)) {
            column = keyword(column, indexName);
        }
        switch (type) {
            case 0: //不参加查询	暂不考虑;
                break;
            case 1: //精确查询	= '匹配值';
                List<String> list = new ArrayList<>();
                list.add(values[0]);
                return initTermsQuery(column, list);
            case 2: //模糊查询	like '%匹配值%';
                return initWildcardQuery(column, "*" + values[0] + "*");
            case 3: //左匹配查询	like '匹配值%';
                return initWildcardQuery(column, values[0] + "*");

            case 4: //区间查询	'匹配值1'<=列<='匹配值2'
                return initRangeQuery(column, values[0], values[1]);

            case 5: //列表查询	不做处理;

                break;
            case 6: //单选对话框	不做处理;

                break;
            case 7: //档案编研公共库查询
                String[] split = values[0].split(",");
                List<String> listgg = Arrays.asList(split);
                return initTermsQuery(column, listgg);
            case 8: //多词检索	分词 以空格为标志;	多词检索中 如果有查询列里的值存在空格 怎么处理 (查询时使用""将带空格需要整体查询的值标记 , 例如:"A B")
                String tenantCode = SecurityUtils.getTenantCode();
                String tableName = tenantCode + "_YQTYC";
                List<String> tyc = elasticsearchApiMapper.selectAppDataList(tableName, "tyc", " primary_word = '" + values[0] + "' and STATUS = '1' ");
                if (ObjectUtils.isNotEmpty(tyc)) {
                    List<Query> queries = new ArrayList<>();
                    tyc.add(values[0]);
                    tyc.forEach(t -> queries.add(initMatchQuery(t)));
                    return initMatchQueryTYC(queries);
                }
                return initMatchQuery(values[0]);
            case 9: //全文精确查询
                List<Query> queries = new ArrayList<>();
                queries.add(MatchPhraseQuery.of(m -> m.query(values[0]).field("pathDocText").slop(0)
                )._toQuery());
                queries.add(MatchPhraseQuery.of(m -> m.query(values[0]).field("pathDocName").slop(0)
                )._toQuery());
                queries.add(MatchPhraseQuery.of(m -> m.query(values[0]).field("pinyin_basis_data").slop(0)
                )._toQuery());
                return Query.of(q -> q.bool(b -> b.should(queries)));
            default:
                break;
        }
        return null;
    }

    /**
     * 判断字段是否拼接 keyword
     *
     * @param field
     * @param indexName
     * @return
     */
    public String keyword(String field, String indexName) {
        ElasticsearchClient client = null;
        try {
            client = ESClientPool.getClient();
            GetMappingRequest request = new GetMappingRequest.Builder().index(indexName.toLowerCase()).build();
            GetMappingResponse mapping = client.indices().getMapping(request);
            String jsonValue = mapping.result().get(indexName.toLowerCase()).mappings().properties().get(field)._kind().jsonValue();
            if ("text".equals(jsonValue)) {
                field = field + ".keyword";
            }
            return field;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            ESClientPool.returnClient(client);
        }
        return field;
    }

}

